﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Core.Services
{
	using System;
	using System.Collections.Generic;
	using Falcon.Core.Collections;
	using Falcon.Core.Domain;
	using Falcon.Core.Domain.Enums;
	using Falcon.Core.Domain.Repositories;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Domain.Values;
	using Falcon.Core.Email;

	public class UserContactService : IUserContactService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="UserContactService"/> class.
		/// </summary>
		/// <param name="identityService">The identity service.</param>
		/// <param name="emailService">The email service.</param>
		/// <param name="userService">The user service.</param>
		/// <param name="userContactRepository">The user contact repository.</param>
		public UserContactService(
			IIdentityService identityService,
			IEmailService emailService, 
			IUserService userService,
			IUserContactRepository userContactRepository)
		{
			this.IdentityService = identityService;
			this.EmailService = emailService;
			this.UserService = userService;
			this.UserContactRepository = userContactRepository;
		}

		/// <summary>
		/// Gets or sets the identity service.
		/// </summary>
		/// <value>The identity service.</value>
		public IIdentityService IdentityService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the email service.
		/// </summary>
		/// <value>The email service.</value>
		public IEmailService EmailService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user service.
		/// </summary>
		/// <value>The user service.</value>
		public IUserService UserService
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets or sets the user contact repository.
		/// </summary>
		/// <value>The user contact repository.</value>
		public IUserContactRepository UserContactRepository
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets the user contact by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserContactSpecification"/>.</param>
		/// <returns></returns>
		public UserContact GetBy(UserContactSpecification specification)
		{
			return this.UserContactRepository.GetBy(specification);
		}

		/// <summary>
		/// Gets the user contact by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public UserContact GetById(int id)
		{
			return this.UserContactRepository.GetById(id);
		}

		/// <summary>
		/// Gets all the user contacts.
		/// </summary>
		/// <returns></returns>
		public IList<UserContact> GetAll()
		{
			return this.UserContactRepository.GetAll();
		}

		/// <summary>
		/// Gets all the user contacts.
		/// </summary>
		/// <param name="specification">The <see cref="UserContactSpecification"/>.</param>
		/// <returns></returns>
		public IPagedList<UserContact, UserContactSpecification> GetPaged(UserContactSpecification specification)
		{
			if (specification.IsModeEnabled)
			{
				ConfigureStatusBasedOnMode(specification);
			}

			return this.UserContactRepository.GetPaged(specification);
		}

		/// <summary>
		/// Creates the user contact.
		/// </summary>
		/// <returns></returns>
		public UserContact Create()
		{
			var contact = new UserContact
			{
				Created = DateTime.Now
			};

			contact.Updated = contact.Created;

			return contact;
		}

		/// <summary>
		/// Gets the count of users contacts using the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public int Count(UserContactSpecification specification)
		{
			if (specification.IsModeEnabled)
			{
				ConfigureStatusBasedOnMode(specification);
			}

			return this.UserContactRepository.Count(specification);
		}

		/// <summary>
		/// Inserts the user contact into the storage.
		/// </summary>
		/// <param name="contact">The <see cref="UserContact"/>.</param>
		public void Insert(UserContact contact)
		{
			this.UserContactRepository.Insert(contact);
		}

		/// <summary>
		/// Inserts the user contact into the storage.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <param name="value">The value.</param>
		public void Insert(UserContact contact, UserContactCreate value)
		{
			value.Validate();

			var identity = IdentityService.Current;

			contact.UserId = identity.Id;

			if (value.Ignore)
			{
				contact.Status = UserContactStatus.Ignored;
			}
			else
			{
				contact.Status = value.Friend ? UserContactStatus.FriendPending : UserContactStatus.Normal;
			}

			var user = this.UserService.GetBy(new UserSpecification
			{
				Name = value.Name
			});

			if (user == null)
			{
				return;
			}

			contact.Contact = user;

			this.Insert(contact);

			if (value.Friend && user.Preference.IsFriendshipNotificationEnabled)
			{
				this.EmailService.Send(new UserFriendshipRequestEmail
				{
					To = user.EmailAddress,
					Name = user.Name,
					Sender = identity.Session.Name
				});
			}
		}

		/// <summary>
		/// Updates the user contact in the storage.
		/// </summary>
		/// <param name="contact">The <see cref="UserContact"/>.</param>
		public void Update(UserContact contact)
		{
			contact.Validate();

			contact.Updated = DateTime.Now;

			this.UserContactRepository.Update(contact);
		}

		/// <summary>
		/// Updates the contacts based on the specified action.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <param name="action">The action.</param>
		public void Update(UserContact contact, UserContactAction action)
		{
			if (action == UserContactAction.RequestFriendship && contact.Status == UserContactStatus.FriendApproved)
			{
				return;
			}

			switch (action)
			{
				case UserContactAction.RequestFriendship:
					{
						contact.Status = UserContactStatus.FriendPending;
					}
					break;
				case UserContactAction.BreakFriendship:
					{
						contact.Status = UserContactStatus.Normal;
						this.UpdateFriend(contact, UserContactStatus.Normal);
					}
					break;
				case UserContactAction.Ignore:
					{
						contact.Status = UserContactStatus.Ignored;
						this.UpdateFriend(contact, UserContactStatus.Normal);
					}
					break;
				default:
					{
						contact.Status = UserContactStatus.Normal;
					}
					break;
			}

			this.Update(contact);
		}

		/// <summary>
		/// Updates the contacts based on the specified action.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <param name="action">The action.</param>
		public void Update(UserContact contact, UserContactFriendAction action)
		{
			if (action == UserContactFriendAction.Approve)
			{
				var friend = this.GetBy(new UserContactSpecification
				{
					UserId = contact.Contact.Id,
					ContactId = contact.User.Id,
					IsModeEnabled = false
				});

				if (friend == null)
				{
					friend = this.Create();

					friend.Status = UserContactStatus.FriendApproved;
					friend.User = contact.Contact;
					friend.Contact = contact.User;

					this.Insert(friend);
				}
				else
				{
					friend.Status = UserContactStatus.FriendApproved;

					this.Update(friend);
				}

				contact.Status = UserContactStatus.FriendApproved;
			}
			else
			{
				contact.Status = UserContactStatus.FriendDenied;
			}

			this.Update(contact);
		}

		/// <summary>
		/// Deletes the user contact in the storage.
		/// </summary>
		/// <param name="contact">The <see cref="UserContact"/>.</param>
		public void Delete(UserContact contact)
		{
			this.UserContactRepository.Delete(contact);
		}

		/// <summary>
		/// Updates the friend.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <param name="status">The status.</param>
		private void UpdateFriend(UserContact contact, UserContactStatus status)
		{
			var friend = this.GetBy(new UserContactSpecification
			{
				UserId = contact.ContactId,
				ContactId = contact.UserId,
				IsModeEnabled = false
			});

			if (friend == null)
			{
				return;
			}

			friend.Status = status;

			this.Update(friend);
		}

		/// <summary>
		/// Configures the status based on mode.
		/// </summary>
		/// <param name="specification">The specification.</param>
		private static void ConfigureStatusBasedOnMode(UserContactSpecification specification)
		{
			switch (specification.Mode)
			{
				case UserContactViewMode.Pending:
					{
						specification.ContactId = specification.UserId;
						specification.Status = new[] { UserContactStatus.FriendPending };
					}
					break;
				case UserContactViewMode.Ignored:
					{
						specification.Status = new[] { UserContactStatus.Ignored };
					}
					break;
				default:
					{
						specification.Status = new[]
						{
							UserContactStatus.Normal, 
							UserContactStatus.FriendApproved, 
							UserContactStatus.FriendDenied, 
							UserContactStatus.FriendPending
						};
					}
					break;
			}
		}
	}
}